﻿using System;
using System.Collections.Generic;
using System.Text;

namespace NullTodoList
{
    /// <summary>
    /// Тип оповещения.
    /// </summary>
    [Flags]
    public enum NotifyKind : int
    {
        /// <summary>
        /// Без оповешения.
        /// </summary>
        None = 0,
        /// <summary>
        /// Всплывающее сообщение.
        /// </summary>
        Alert = 1,
        /// <summary>
        /// SMS.
        /// </summary>
        SMS = 2,
        /// <summary>
        /// Email.
        /// </summary>
        Mail = 4
    }
    
    /// <summary>
    /// Аргумент для события запроса тэгов.
    /// </summary>
    public sealed class RequestTagsEventArgs: EventArgs
    {
        /// <summary>
        /// Конструктор со списком тэгов.
        /// </summary>
        /// <param name="tagList"></param>
        public RequestTagsEventArgs(List<Int32> tagList)
        {
            TagList = tagList;     
        }

        /// <summary>
        /// Список тэгов.
        /// </summary>
        public List<Int32> TagList
        {
            get; 
            private set;
        }
    }

    /// <summary>
    /// Класс реализует задание.
    /// </summary>
    public sealed class TodoItem : ICloneable
    {
        private static String[] DATE_RANGE_NAMES = new String[] { "позавчера", "вчера", "сегодня", "завтра", "послезавтра" };
        /// <summary>
        /// Дело.
        /// </summary>
        public const Int32 CATEGORY_TASK = 1;
        /// <summary>
        /// Процесс.
        /// </summary>
        public const Int32 CATEGORY_PROCESS = 2;
        /// <summary>
        /// Заметка.
        /// </summary>
        public const Int32 CATEGORY_REMINDER = 4;
        /// <summary>
        /// Недействительный индектификатор.
        /// </summary>
        public const Int32 INVALID_ITEM = -1;

        private List<Int32> tags;
        
        /// <summary>
        /// Конструктор по-умолчанию.
        /// </summary>
        public TodoItem()
        {
        }

        /// <summary>
        /// Загрузка задания из БД.
        /// </summary>
        /// <param name="row">todoListDataSet.itemsRow</param>
        /// <returns>Экзмепляр класса.</returns>
        public static TodoItem FromRow(todoListDataSet.itemsRow row)
        {
            var item = new TodoItem();
            item.ID = row.id;
            item.Category = row.category;
            item.Text = row.text;
            item.Comment = row.comment;
            item.Prioritet = row.prioritet;
            item.IsComplete = row.isComplete;
            item.DateBegin = row.dateBegin;
            item.Length = row.length;
            item.Notify = (NotifyKind)row.notify;
            item.Parent = row.parent;
            return item;
        }

        /// <summary>
        /// Парсинг даты.
        /// Метод распознает такие понятия, как "вчера", "завтра" и т.д.
        /// В случае совпадения с этими понятиями значение date изменяется
        /// в соотвествии с полученным значением.
        /// Также метод парсит числовое представление даты.
        /// </summary>
        /// <param name="source">Исходная строка.</param>
        /// <param name="date">Дата.</param>
        /// <returns>Результат парсинга.</returns>
        private static Boolean ParseDate(String source, ref DateTime date)
        {
            // Сравниваем с "сегодня", "завтра" и т.п.
            var idx = Array.IndexOf(DATE_RANGE_NAMES, source);
            if (idx != -1)
            {
                // Изменяем дату на нужное количество дней.
                date = date.AddDays(idx - 2);
                return true;
            }
            DateTime temp;
            if (DateTime.TryParse(source, out temp))
            {
                date = temp;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Парсинг времени.
        /// Время может быть представлено как в виде одиночной цифрой (часы)
        /// так и часы/минуты через разделитель.
        /// </summary>
        /// <param name="source">Исходная строка.</param>
        /// <param name="time">Время.</param>
        /// <returns>Результат парсинга.</returns>
        private static Boolean ParseTime(String source, ref TimeSpan time)
        {
            // Попытка распарсить одиночное значение.
            Int32 hour;
            if (Int32.TryParse(source, out hour))
            {
                // Проверяем, входит ли число в диапазон 0-23.
                if (hour >= 0 && hour < 24)
                {
                    time = new TimeSpan(hour, 0, 0);
                    return true;
                }
            }
            else
            {
                // Попытка распарсить двойное значение с разделителем.
                TimeSpan temp;
                if (TimeSpan.TryParse(source, out temp))
                {
                    time = temp;
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Создание задания из строки.
        /// </summary>
        /// <param name="source">Исходная строка.</param>
        /// <returns>Задание.</returns>
        public static TodoItem FromString(String source)
        {
            var date = DateTime.Now;
            var from = date.TimeOfDay;
            var to = date.TimeOfDay;
            date = date.Date;
            var dateParsed = false;
            var timeParsed = false;
            var category = TodoItem.CATEGORY_TASK;
            var text = new StringBuilder();
            var parts = source.Split(new Char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            for (var i = 0; i < parts.Length; i++)
            {
                var str = parts[i];
                if (!dateParsed)
                {
                    if (ParseDate(str, ref date))
                    {
                        dateParsed = true;
                        continue;
                    }
                }
                if (!timeParsed)
                {
                    if (String.Equals(str, "в", StringComparison.CurrentCultureIgnoreCase))
                    {
                        var temp = parts.GetValueOrDefault(i + 1, String.Empty);
                        if (ParseTime(temp, ref from))
                        {
                            timeParsed = true;
                            i++;
                            continue;
                        }
                    }
                    if (String.Equals(str, "с", StringComparison.CurrentCultureIgnoreCase))
                    {
                        var temp = parts.GetValueOrDefault(i + 2, String.Empty);
                        if (String.Equals(temp, "до", StringComparison.CurrentCultureIgnoreCase))
                        {
                            if (ParseTime(parts.GetValueOrDefault(i + 1, String.Empty), ref from)
                                && ParseTime(parts.GetValueOrDefault(i + 3, String.Empty), ref to))
                            {
                                timeParsed = true;
                                category = TodoItem.CATEGORY_PROCESS;
                                i += 3;
                                continue;
                            }
                        }
                    }
                }
                if (text.Length != 0)
                    text.Append(' ');
                text.Append(str);
            }

            var item = new TodoItem();
            item.Text = text.ToString();
            item.Category = category;
            item.DateBegin = date;
            if (timeParsed)
            {
                item.DateBegin += from;
                if (item.Category == TodoItem.CATEGORY_PROCESS)
                {
                    item.Length = (Int32)((to - from).TotalMinutes);
                    if (item.Length < 0)
                        item.Length = 0;
                }
            }
            return item;
        }

        /// <summary>
        /// Клонирование задания.
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
            return MemberwiseClone();
        }

        /// <summary>
        /// Представление задания в текстовом виде.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Text;
        }

        /// <summary>
        /// Список ИД тэгов.
        /// </summary>
        public List<Int32> Tags
        {
            get
            {
                if (tags == null)
                {
                    tags = new List<Int32>();
                    if (RequestTags != null)
                        RequestTags(this, new RequestTagsEventArgs(tags));
                }
                return tags;
            }
        }

        /// <summary>
        /// ИД задания.
        /// </summary>
        public Int32 ID 
        {
            get; set;
        }

        /// <summary>
        /// Категория.
        /// </summary>
        public Int32 Category
        {
            get;
            set;
        }

        /// <summary>
        /// Текст.
        /// </summary>
        public String Text
        {
            get;
            set;
        }

        /// <summary>
        /// Комментарий.
        /// </summary>
        public String Comment
        {
            get;
            set;
        }

        /// <summary>
        /// Приоритет.
        /// </summary>
        public Int32 Prioritet
        {
            get;
            set;
        }

        /// <summary>
        /// Флаг выполнения задания.
        /// </summary>
        public Boolean IsComplete
        {
            get;
            set;
        }

        /// <summary>
        /// Дата задания.
        /// </summary>
        public DateTime DateBegin
        {
            get;
            set;
        }

        /// <summary>
        /// Продолжительность процесса.
        /// </summary>
        public Int32 Length
        {
            get;
            set;
        }

        /// <summary>
        /// Уведомления.
        /// </summary>
        public NotifyKind Notify
        {
            get;
            set;
        }

        ///// <summary>
        ///// 
        ///// </summary>
        //public Int32 Group
        //{
        //    get;
        //    set;
        //}

        /// <summary>
        /// ИД родительского задания.
        /// </summary>
        public Int32 Parent
        {
            get;
            set;
        }

        /// <summary>
        /// Событие-запрос списка ИД тэгов.
        /// </summary>
        public event EventHandler<RequestTagsEventArgs> RequestTags;
    }

    /// <summary>
    /// Класс реализаует тэг.
    /// </summary>
    class Tag
    {
        /// <summary>
        /// Конструктор с параметрами.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="name"></param>
        public Tag(Int32 id, String name)
        {
            ID = id;
            Name = name;
        }
        
        // ИД тэга.
        public Int32 ID
        {
            get;
            private set;
        }

        /// <summary>
        /// Имя.
        /// </summary>
        public String Name
        {
            get;
            set;
        }

        public override string ToString()
        {
            return Name;
        }

    }

    /// <summary>
    /// Хелпер для массива строк.
    /// </summary>
    public static class ArrayHelper
    {
        /// <summary>
        /// Получение строки по указанному индексу, либо возврат указанного
        /// значения по-умолчанию, если индекс выходит за пределы массива.
        /// </summary>
        /// <param name="array">Исходный массив.</param>
        /// <param name="index">Индекс элемента.</param>
        /// <param name="_default">Значение по-умолчанию.</param>
        /// <returns>Значение по индексу, либо значение по-умолчанию.</returns>
        public static String GetValueOrDefault(this String[] array, Int32 index, String _default)
        {
            return index >= 0 && index < array.Length ? array[index] : _default;
        }
    }
}
